`define FULL_ADDER(num, A, B, C_IN) \
  full_adder mul_full_adder_``num( \
    .a(``A), \
    .b(``B), \
    .c_in(``C_IN), \
    .s(adder_s[``num]), \
    .c_out(adder_c_out[``num]) \
  )

module mul(
  input mul_clk,
  input resetn,
  input mul_signed,
  input [31:0] x,
  input [31:0] y,

  output [63:0] result
);

  wire [67:0] x_68 = {{36{mul_signed & x[31]}}, x};
  wire [34:0] y_35 = {{2{mul_signed & y[31]}}, y, 1'b0};

  wire [16:0] gpp_c;
  wire [67:0] gpp_p [16:0];
  reg [15:14] gpp_c_r;

  wire [16:0] wt_a [67:0];
  wire [13:0] wt_c_out [67:0];
  wire [67:0] wt_c;
  wire [67:0] wt_s;
  reg [67:0] wt_c_r;
  reg [67:0] wt_s_r;

  wire        adder_c_in;
  wire [64:0] adder_a;
  wire [64:0] adder_b;
  wire [64:0] adder_result;
  genvar i, k;
  integer j;

  generate
    for (i = 0; i < 17; i = i + 1) begin
    gen_partial_product gpp(
      .x({x_68[67 - 2 * i:0], {(2 * i){1'b0}}}),
      .y({y_35[i * 2 + 2], y_35[i * 2 + 1], y_35[i * 2]}),
      .c(gpp_c[i]),
      .p(gpp_p[i])
    );
    end      
  endgenerate

  generate
    for (i = 0; i < 68; i = i + 1) begin
      for (k = 0; k < 17; k = k + 1) begin
        assign wt_a[i][k] = gpp_p[k][i];
      end
    end
  endgenerate

  generate
    wallace_tree mul_wallace_tree_0(
      .a(wt_a[0]),
      .c_in(gpp_c[13:0]),

      .c_out(wt_c_out[0]),
      .c(wt_c[0]),
      .s(wt_s[0])
    );
    for (i = 1; i < 68; i = i + 1) begin
      wallace_tree mul_wallace_tree(
        .a(wt_a[i]),
        .c_in(wt_c_out[i - 1]),

        .c_out(wt_c_out[i]),
        .c(wt_c[i]),
        .s(wt_s[i])
      );
    end
  endgenerate

  always@(posedge mul_clk) begin
    for (j = 0; j < 68; j = j + 1) begin
      if (!resetn) begin
        wt_c_r[j] <= 1'd0;
        wt_s_r[j] <= 68'd0;
      end else begin
        wt_c_r[j] <= wt_c[j];
        wt_s_r[j] <= wt_s[j];
      end
    end
    if (!resetn) begin
      gpp_c_r[15] <= 1'd0;
      gpp_c_r[14] <= 1'd0;
    end else begin
      gpp_c_r[15] <= gpp_c[15];
      gpp_c_r[14] <= gpp_c[14];
    end
  end

  assign adder_c_in = gpp_c_r[14];
  assign adder_a[0] = adder_c_in;
  assign adder_b[0] = adder_c_in;
  generate
    for (i = 0; i < 64; i = i + 1) begin
      assign adder_a[i + 1] = wt_s_r[i];
    end
    assign adder_b[1] = gpp_c_r[15];
    for (i = 1; i < 64; i = i + 1) begin
      assign adder_b[i + 1] = wt_c_r[i - 1];
    end
  endgenerate
  assign adder_result = adder_a + adder_b;
  assign result = adder_result[64:1];

endmodule

module gen_partial_product(
  input [67:0] x,
  input [ 2:0] y,
  
  output c,
  output [67:0] p
);

  wire [67:0] x_bar       = ~x;
  wire [67:0] x_shift     = {x[66:0], 1'b0};
  wire [67:0] x_shift_bar = ~x_shift;

  assign c = y[2] & (~y[1] | ~y[0]) ;

  assign p = y[2] ? 
    (y[1] ? 
      (y[0] ? 68'd0   : x_bar      ) :
      (y[0] ? x_bar   : x_shift_bar)
    ) :
    (y[1] ?
      (y[0] ? x_shift : x          ) :
      (y[0] ? x       : 68'd0      )
    );

endmodule

module wallace_tree(
  input [16:0] a,
  input [13:0] c_in,

  output [13:0] c_out,
  output s,
  output c
);
  wire [14:0] adder_c_out;
  wire [14:0] adder_s;

  `FULL_ADDER(0, a[4], a[3], a[2]);
  `FULL_ADDER(1, a[7], a[6], a[5]);
  `FULL_ADDER(2, a[10], a[9], a[8]);
  `FULL_ADDER(3, a[13], a[12], a[11]);
  `FULL_ADDER(4, a[16], a[15], a[14]);

  `FULL_ADDER(8, adder_s[4], adder_s[3], adder_s[2]);
  `FULL_ADDER(7, adder_s[1], adder_s[0], a[1]);
  `FULL_ADDER(6, a[0], c_in[4], c_in[3]);
  `FULL_ADDER(5, c_in[2], c_in[1], c_in[0]);

  `FULL_ADDER(10, adder_s[8], adder_s[7], adder_s[6]);
  `FULL_ADDER(9, adder_s[5], c_in[6], c_in[5]);

  `FULL_ADDER(12, adder_s[10], adder_s[9], c_in[10]);
  `FULL_ADDER(11, c_in[9], c_in[8], c_in[7]);

  `FULL_ADDER(13, adder_s[12], adder_s[11], c_in[11]);

  `FULL_ADDER(14, adder_s[13], c_in[13], c_in[12]);

  assign c_out = adder_c_out[13:0];
  assign s = adder_s[14];
  assign c = adder_c_out[14];

endmodule


module full_adder(
  input a,
  input b,
  input c_in,

  output s,
  output c_out 
);

  assign s = 
     a &  b &  c_in | 
     a & ~b & ~c_in |
    ~a &  b & ~c_in |
    ~a & ~b &  c_in ;
  assign c_out = 
    a & b    | 
    a & c_in |
    b & c_in ;

endmodule
